package windows;


import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;

import data_types.ComplexField;
import data_types.ParameterField;
import data_types.ParameterName;
import data_types.SimpleField;

public class MethodWindow {

	private static final int COMPONENT_HEIGHT = 60;
	private static final int ERROR_MESSAGE_HEIGHT = 30;
	private static final int NUM_COLUNMS = 2;
	private static final int NUM_ROWS_FOR_BUTTONS = 1;

	private JFrame window = new JFrame();
	private Container container = window.getContentPane();
	private ArrayList<ParameterField> fields = new ArrayList<ParameterField>();
	private JLabel errorMessageLabel = new JLabel();

	private Method method;
	private Object object;
	private JFrame mainWindow;

	private boolean validValues = true;
	private int numberOfFields = 0;

	public MethodWindow(Method method, Object object, JFrame mainWindow) {
		this.method = method;
		this.object = object;
		this.mainWindow = mainWindow;
		windowFormat();
		init();
	}
	
	public void windowFormat() {
		window.setTitle(method.getName());
		formating();
		container.setLayout(new BorderLayout());
		JPanel centralPane = new JPanel();
		centralPane.setLayout(new GridLayout(numberOfFields + NUM_ROWS_FOR_BUTTONS, NUM_COLUNMS));
		for (ParameterField field : fields) {
			if (field.getClass().equals(SimpleField.class)) {
				centralPane.add(((SimpleField) field).getLabel());
				centralPane.add(((SimpleField) field).getComponent());
			} else if (field.getClass().equals(ComplexField.class)) {
				addParameterToPanel((ComplexField) field, centralPane);
			}
		}
		JButton confirmButton = new JButton("Confirm");
		JButton cancelButton = new JButton("Cancel");
		confirmButton.addActionListener(new ConfirmActionListener());
		cancelButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				window.setVisible(false);
				window.dispose();
			}
		});
		centralPane.add(confirmButton);
		centralPane.add(cancelButton);
		container.add(centralPane, BorderLayout.CENTER);
		window.setSize(325, ((numberOfFields * COMPONENT_HEIGHT) / NUM_COLUNMS) + COMPONENT_HEIGHT);
		window.setLocation(mainWindow.getLocation().x + mainWindow.getWidth() / 2 - window.getWidth() / 2, mainWindow.getLocation().y + mainWindow.getHeight() / 2 - window.getHeight() / 2);
	}

	public void addParameterToPanel(ComplexField field, JPanel centralPane) {
		for (ParameterField pField : field.getList()) {
			if (pField.getClass().equals(SimpleField.class)) {
				centralPane.add(((SimpleField) pField).getLabel());
				centralPane.add(((SimpleField) pField).getComponent());
			} else if (pField.getClass().equals(ComplexField.class)) {
				addParameterToPanel((ComplexField) pField, centralPane);
			}
		}
	}

	private void formating() {
		int it_method_parametersName = 0;
		ArrayList<ParameterField> auxList = new ArrayList<>();
		for (Type type : method.getParameterTypes()) {
			ParameterName pm = new ParameterName();
			try {
				List<String> parameterNames = pm.getParameterNames(method);
				if (((Class<?>) type).isPrimitive() || type.equals(String.class) || Collection.class.isAssignableFrom(type.getClass())) {
					if(type.equals(boolean.class)){
						String pmName = parameterNames.get(it_method_parametersName);
						ParameterField field = new SimpleField(new JCheckBox(), new JLabel(pmName), type);
						auxList.add(field);
						fields.add(field);
						numberOfFields++;
					}else{
						String pmName = parameterNames.get(it_method_parametersName);
						ParameterField field = new SimpleField(new JTextField(), new JLabel(pmName), type);
						auxList.add(field);
						fields.add(field);
						numberOfFields++;
					}
				} else if (((Class<?>) type).isEnum()) {
						Class<?> c = (Class<?>) type;
						String pmName = parameterNames.get(it_method_parametersName);
						JComboBox<?> comboBox = new JComboBox<>(c.getEnumConstants());
						ParameterField field = new SimpleField(comboBox, new JLabel(" " + pmName), type);
						fields.add(field);
						numberOfFields++;
					}else {
						Constructor<?>[] listConst = ((Class<?>) type).getConstructors();
						Constructor<?> constructor = getConstructor(listConst);
						fields.add(checkConstructorParameters(constructor, null));
					}
				it_method_parametersName++;
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	private ParameterField checkConstructorParameters(Constructor<?> constructorParameter, Constructor<?> constructorUsed) {
		try {
			ParameterName pm = new ParameterName();
			List<String> parameterNames = pm.getParameterNames(constructorParameter);
			int it_constructor_parameterNames = 0;
			ArrayList<ParameterField> auxList = new ArrayList<>();
			ParameterField field = new ComplexField(auxList, constructorParameter);
			for (Type type : constructorParameter.getParameterTypes()) {
				if (((Class<?>) type).isPrimitive() || type.equals(String.class) || Collection.class.isAssignableFrom(type.getClass())) {
					ParameterField wf = new SimpleField(new JTextField(), new JLabel(" " + parameterNames.get(it_constructor_parameterNames)), type);
					auxList.add(wf);
					numberOfFields++;
					it_constructor_parameterNames++;
				} else if (((Class<?>) type).isEnum()) {
					Class<?> c = (Class<?>) type;
					String pmName = parameterNames.get(it_constructor_parameterNames);
					JComboBox<?> comboBox = new JComboBox<>(c.getEnumConstants());
					ParameterField pField = new SimpleField(comboBox, new JLabel(" " + pmName), type);
					fields.add(pField);
					numberOfFields++;
				}else {
					Constructor<?>[] listConst = ((Class<?>) type).getConstructors();
					auxList.add(checkConstructorParameters(getConstructor(listConst), constructorParameter));
				}
			}
			return field;
		} catch (IOException | NullPointerException e) {
			e.printStackTrace();
		}
		return null;
	}

	private Constructor<?> getConstructor(Constructor<?>[] listConst) {
		Constructor<?> constructorParameter = null;
		int auxSize = 0;
		for (Constructor<?> cons : listConst) {
			if (auxSize >= cons.getParameterTypes().length || auxSize == 0) {
				auxSize = cons.getParameterTypes().length;
				constructorParameter = cons;
			}
		}
		return constructorParameter;
	}

	public void init() {
		window.setVisible(true);
	}

	class ConfirmActionListener implements ActionListener {
		@Override
		public void actionPerformed(ActionEvent e) {
			extractInformationFromFields();
			if (validValues) {
//				showValues();
				try {
					invokeMethod();
				} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | InstantiationException ex) {
					ex.printStackTrace();
				}
				window.dispose();
			}
		}

		private void invokeMethod() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException {
			Object[] arguments = new Object[method.getParameterTypes().length];
			int it_args = 0;
			for (ParameterField pField : fields) {
				if(pField.getClass().equals(SimpleField.class)){
					SimpleField field = (SimpleField) pField;
					if (field.getComponent().getClass().equals(JTextField.class)) {
						if (field.getType().equals(String.class)) {
							arguments[it_args] = ((JTextField) field.getComponent()).getText();
							it_args++;
						} else if (field.getType().equals(int.class)) {
							arguments[it_args] = Integer.parseInt(((JTextField) field.getComponent()).getText());
							it_args++;
						}
					}else if (field.getComponent().getClass().equals(JCheckBox.class)) {
						arguments[it_args] = ((JCheckBox) field.getComponent()).isSelected();
					}else if (field.getComponent().getClass().equals(JComboBox.class)){
						arguments[it_args] = (((JComboBox<?>)field.getComponent()).getSelectedItem());
						it_args++;
					}
				}else if(pField.getClass().equals(ComplexField.class)){
					arguments[it_args] = argumentsOfComplexFields((ComplexField) pField);
					it_args++;
				}
			}
			try{
				method.invoke(object, arguments);
			}catch(Exception ex){
				if(ex.getCause().getMessage() != null){
					JOptionPane.showMessageDialog(window, ex.getCause().getMessage());
				} else {
					JOptionPane.showMessageDialog(window, "Error. An Excepction was thrown.");
				}
			}
		}
		
		private Object argumentsOfComplexFields(ComplexField complexField) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{
			Object[] arguments = new Object[complexField.getConstructor().getParameterTypes().length];
			int it_args = 0;
			for (ParameterField pField : complexField.getList()) {
				if(pField.getClass().equals(SimpleField.class)){
					SimpleField field = (SimpleField) pField;
					if (field.getComponent().getClass().equals(JTextField.class)) {
						if (field.getType().equals(String.class)) {
							arguments[it_args] = ((JTextField) field.getComponent()).getText();
							it_args++;
						} else if (field.getType().equals(int.class)) {
							arguments[it_args] = Integer.parseInt(((JTextField) field.getComponent()).getText());
							it_args++;
						}else if (field.getComponent().getClass().equals(JComboBox.class)){
							arguments[it_args] = (((JComboBox<?>)field.getComponent()).getSelectedItem());
							it_args++;
						}
					}
				}else if(pField.getClass().equals(ComplexField.class)){
					arguments[it_args] = argumentsOfComplexFields((ComplexField) pField);
					it_args++;
				}
			}
			return complexField.getConstructor().newInstance(arguments);
		}
		
		private void extractInformationFromFields() {
			validValues = true;
			for (ParameterField pField : fields) {
				if (pField.getClass().equals(SimpleField.class)) {
					SimpleField field = (SimpleField) pField;
					if (field.getComponent().getClass().equals(JTextField.class)) {
						if (isInvalid((JTextField) field.getComponent(), field.getType())) {
							errorMessageLabel.setText("ERROR: The field '" + field.getLabel().getText() + "' expected a number");
							container.add(errorMessageLabel, BorderLayout.NORTH);
							window.setSize(window.getWidth(), window.getHeight() + ERROR_MESSAGE_HEIGHT);
							window.validate();
							validValues = false;
						}
						field.setValue(((JTextField) field.getComponent()).getText());
					} else if (field.getComponent().getClass().equals(JComboBox.class)) {
						Object o = ((JComboBox<?>)field.getComponent()).getSelectedItem();
						field.setValue(o);
					} else if (field.getComponent().getClass().equals(JCheckBox.class)) {
						field.setValue(((JCheckBox) field.getComponent()).isSelected());
					}
				} else if (pField.getClass().equals(ComplexField.class)) {
					extractValuesFromComplexFields((ComplexField) pField);
				}
			}
		}

		private void extractValuesFromComplexFields(ComplexField complexField) {
			for (ParameterField pField : complexField.getList()) {
				if (pField.getClass().equals(SimpleField.class)) {
					SimpleField field = (SimpleField) pField;
					if (field.getComponent().getClass().equals(JTextField.class)) {
						if (isInvalid((JTextField) field.getComponent(), field.getType())) {
							errorMessageLabel.setText("ERROR: The field '" + field.getLabel().getText() + "' expected a number");
							container.add(errorMessageLabel, BorderLayout.NORTH);
							window.setSize(window.getWidth(), window.getHeight() + ERROR_MESSAGE_HEIGHT);
							window.validate();
							validValues = false;
						}
						field.setValue(((JTextField) field.getComponent()).getText());
					} else if (field.getComponent().getClass().equals(JComboBox.class)) {
						Object o = ((JComboBox<?>)field.getComponent()).getSelectedItem();
						field.setValue(o);
					} else if (field.getComponent().getClass().equals(JCheckBox.class)) {
						field.setValue(((JCheckBox) field.getComponent()).isSelected());
					}
				} else if (pField.getClass().equals(ComplexField.class)) {
					extractValuesFromComplexFields((ComplexField) pField);
				}
			}
		}

		private boolean isInvalid(JTextField textField, Type type) {
			String text = textField.getText();
			if (type.equals(int.class) || type.equals(Integer.class)) {
				try {
					Integer.parseInt(text);
				} catch (NumberFormatException e) {
					return true;
				}
			} else if (type.equals(double.class) || type.equals(Double.class)) {
				try {
					Double.parseDouble(text);
				} catch (NumberFormatException e) {
					return true;
				}
			}
			return false;
		}
	}

	public void showValues() {
		for (ParameterField field : fields) {
			if (field.getClass().equals(SimpleField.class)) {
				System.out.println(((SimpleField) field).getLabel().getText() + ": " + ((SimpleField) field).getValue());
			} else if (field.getClass().equals(ComplexField.class)) {
				showValuesFromComplexField((ComplexField) field);
			}
		}
	}

	public void showValuesFromComplexField(ComplexField complexField) {
		for (ParameterField field : complexField.getList()) {
			if (field.getClass().equals(SimpleField.class)) {
				System.out.println(((SimpleField) field).getLabel().getText() + ": " + ((SimpleField) field).getValue());
			} else if (field.getClass().equals(ComplexField.class)) {
				showValuesFromComplexField((ComplexField) field);
			}
		}
	}
}